/**
 * 
 */
package parser;

import game.MoveExecutionList;

import java.awt.Point;
import java.util.ArrayList;
import java.util.Stack;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;

import chess.Board;
import chess.ChessClock;
import chess.Player;
import chess.Tile;

import command.*;

/**
 * This method is an aid to the parser. This will deal with all the token that are parsed.
 * It will take the tokens plus any additional attributes and create the command objects for
 * the chess game.
 * @author Caleb Chan
 *
 */
public class ChessContentHandler extends ContentHandlerAdapter implements ChessContentHandlerConstants, ParserErrorConstants {
	
	private Stack<String> currentElement;
	private Board board;
	private Tile tile1;
	private Tile tile2;
	private Point point;
	private String pointType;
	private String promotePiece;
	private Command command;
	private MoveExecutionList moveList;
	private int commandNum;
	private ArrayList<Player> players;
	private Player currentPlayer;
	private ChessClock clock;
	private long time;
	
	private int errorCode;
	private boolean isCorrectGame;
	private boolean isTimeSet;

	public ChessContentHandler(Board b, ArrayList<Player> players){
		board = b;
		this.players = players;
		currentElement = new Stack<String>();
		point = new Point(ChessContentHandlerConstants.DEFAULT_POINT);
		pointType = "";
		moveList = new MoveExecutionList();
		commandNum = 1;
		currentPlayer = players.get(0);
		errorCode = FILE_SUCCESSFULLY_OPENED;
		isCorrectGame = false;
		isTimeSet = false;
		time = 0;
		if(board.getGame() instanceof game.ChessGame){
			clock = ((game.ChessGame)board.getGame()).getClock();
			//clock.resetClock();
			//System.out.println("Current Player : " + clock.getCurPlayer().getColour());
		}
	}
	/**
	 * This method is called when the parser reaches the end tag in XML. It will handle all
	 * the creation of the tile from the different points as well as place then into a list
	 * @param uri The URI where the end element is mapped to 
	 * @param localName The name (without prefix) of the tag being called
	 * @param qName The qualified name (with prefix) of the tag
	 * @throws SAXException The exception thrown if there is an error
	 */
	@Override
	public void endElement(String uri, String localName, String qName)	throws SAXException {
		
		if(currentElement.peek().equals(ChessContentHandlerConstants.ELEMENT_POINT)){
			if(pointType.equals(ChessContentHandlerConstants.POINT_1)){
				tile1 = board.getTile((int)point.getX(), (int)point.getY());
			}else if(pointType.equals(ChessContentHandlerConstants.POINT_2)){
				tile2 = board.getTile((int)point.getX(), (int)point.getY());		
			}
		}else if(currentElement.peek().equals(ChessContentHandlerConstants.ELEMENT_COMMMAND)){
			moveList.add(command);
			//System.out.println();
			switchPlayer();
		}

		handleElementCommands(localName,commandNum);

		String startElement = currentElement.pop();
		if(startElement != localName){
			errorCode = ELEMENT_PARSE_ERROR;
			throw new SAXException();
		}
	}
	/**
	 * This method will switch between player to allow for correct pieces to be taken when
	 * creating the attack commands.
	 */
	private void switchPlayer(){
		if(players.size() == 2){
			if(currentPlayer.equals(players.get(0))){
				currentPlayer = players.get(1);
			}else{
				currentPlayer = players.get(0);
			}
		}
	}
	/**
	 * This method will handle the command creation. It will also execute the commands and 
	 * also create any secondary commands if necessary.
	 * @param command The command string that is to be parsed and turn into a command object
	 * @param commandNumber The command number telling if the commnad is a primary command or 
	 * a secondary command.
	 */
	private void handleElementCommands(String command, int commandNumber)throws SAXException{
		Command tempCommand = null;
		try {
			if(command.equals(ELEMENT_MOVE)){
				tempCommand = new MoveCommand(tile1,tile2);
			}else if(command.equals(ELEMENT_ATTACK)){
				tempCommand = new AttackCommand(tile1,tile2,currentPlayer);
			}else if(command.equals(ELEMENT_CASTLE)){
				tempCommand = new CastleCommand(tile1,tile2);
			}else if(command.equals(ELEMENT_ENPASSANT)){
				tempCommand = new EnPassantAttackCommand(tile1,tile2,currentPlayer);
			}else if(command.equals(ELEMENT_NO_COMMAND)){
				tempCommand = new NoSecondaryCommand();
			}else if(command.equals(ELEMENT_PROMOTE)){
				tempCommand = new PromoteCommand(promotePiece, tile1);
			}else if(command.equals(ELEMENT_PLACE)){
				tempCommand = new PiecePlaceCommand(tile1,promotePiece,currentPlayer);
			}else if(command.equals(ELEMENT_KNIGHT_MOVE)){
				tempCommand = new KnightsTourMoveCommand(tile1,tile2);
			}else if(command.equals(ELEMENT_MOVE_CHANGE)){
				tempCommand = new ChessMoveCountCommand((int)point.getX(),(int)point.getY(),(game.ChessGame)board.getGame());
			}else if(command.equals(ELEMENT_CHESS_CLOCK)){
				if(isTimeSet){
					tempCommand = new ChessClockCommand((game.ChessGame)board.getGame(), time, currentPlayer);
					isTimeSet = false;
				}
			}
			if(tempCommand == null){
				errorCode = COMMAND_FORMAT_ERROR;
				return;
			}
			//System.out.println("Player : " + currentPlayer.getColour() + " Num : " + commandNum + " Command : " + tempCommand);
			tempCommand.execute();
		} catch (Exception e) {
			errorCode = COMMAND_FORMAT_ERROR;
			/*e.printStackTrace();
			board.drawBoard();
			System.out.println("Command : " + ((this.command == null)?"NULL":this.command.toString()));
			System.out.println("Tile 1 : " + tile1.toString() + " Tile 2" + tile2.toString());
			System.out.println("Command : " + command + " Num : " + commandNumber);
			System.out.println("Stack : " + currentElement.toString());
			System.out.println("Player : " + currentPlayer.getColour());*/
			throw new SAXException();
		}
		if(commandNumber == ChessContentHandlerConstants.FIRST_COMMAND){
			this.command = tempCommand;
			switchCommand();
		}else{
			this.command.addSecondaryCommand(tempCommand);
			if(tempCommand instanceof NoSecondaryCommand){
				switchCommand();
			}
		}
		
	}
	/**
	 * This method will switch between primary and secondary commands so the parser knows
	 * if the command being parsed is a primary or secondary command.
	 */
	private void switchCommand(){
		if(commandNum == ChessContentHandlerConstants.FIRST_COMMAND){
			commandNum = ChessContentHandlerConstants.SECOND_COMMAND;
		}else{
			commandNum = ChessContentHandlerConstants.FIRST_COMMAND;
		}
	}
	/**
	 * This method will be called any time the parser reads the opening tag in XML.
	 * This method will initialize any variables needed when parsing a certain 
	 * tag. It will also use the attributes in the tag to aid in the creation of the 
	 * different parts of the commands.
	 * @param uri The URI where the end element is mapped to 
	 * @param localName The local name (without prefix) of the tag
	 * @param qName The qualified name (with prefix) of the tag
	 * @param atts The attributes of the tag and the values they contain
	 * @throws SAXException If anything goes wrong with parsing and exception is thrown
	 */
	@Override
	public void startElement(String uri, String localName, String qName,Attributes atts) throws SAXException {
		if(board.getGame() instanceof game.ChessGame && localName.equals(MAIN_TAG_CHESS_GAME)){
			isCorrectGame = true;
		}else if(board.getGame() instanceof game.KnightsTourGame && localName.equals(MAIN_TAG_KNIGHTS_TOUR_GAME)){
			isCorrectGame = true;
		}else if(board.getGame() instanceof game.EightQueensGame && localName.equals(MAIN_TAG_EIGHTS_QUEENS_GAME)){
			isCorrectGame = true;
		}
		if(!isCorrectGame){
			errorCode = ParserErrorConstants.INVALID_GAME_TYPE_ERROR;
			throw new SAXException();
		}
		currentElement.push(localName);
		handleStartElement(currentElement.peek());
		handleStartAttribute(currentElement.peek(),atts);
	}
	/**
	 * This method will handle the start element of the tag. If the element is equal to
	 * "Point", then the method will initialize the point object to the default value so
	 * new value of point are not affected by the old values. This is also the case for 
	 * elements equals to "Piece".
	 * @param element The start element that is to be compared to
	 */
	private void handleStartElement(String element){
		if(element.equals(ChessContentHandlerConstants.ELEMENT_POINT)){
			point = new Point(ChessContentHandlerConstants.DEFAULT_POINT);
		}else if(element.equals(ChessContentHandlerConstants.ELEMENT_PIECE)){
			promotePiece = "";	
		}else if(element.equals(ChessContentHandlerConstants.ELEMENT_COUNTER)){
			point = new Point(ChessContentHandlerConstants.DEFAULT_POINT);
		}
	}
	/**
	 * This method will handle the start attributes of the tag. Using the attributes, it 
	 * will set the promoted piece or get which type of point is to be created
	 * @param element The element that holds the attributes
	 * @param atts The attributes associated with the element and their values
	 */
	private void handleStartAttribute(String element,Attributes atts)throws SAXException{
		if(atts.getLength() == 1){
			String attsName = atts.getLocalName(ChessContentHandlerConstants.ATTRIBUTE_1);
			if(attsName.equals(ChessContentHandlerConstants.ATTRIBUTE_VALUE)){
				String value  = atts.getValue(ChessContentHandlerConstants.ATTRIBUTE_1);
				if(element.equals(ChessContentHandlerConstants.ELEMENT_PIECE)){
					promotePiece = value;
				}else if(!pointType.equals("")){
					handleStartValue(value,currentElement.peek());
					handleCounterValue(value,currentElement.peek());
					handleClockValue(value,currentElement.peek());
				}else if(element.equals(ChessContentHandlerConstants.ELEMENT_GAME_TYPE)){
					promotePiece = value;
					if(!promotePiece.equals(ChessContentHandlerConstants.GAME_TYPE_CHESS)){
						errorCode = ParserErrorConstants.COUNT_NOT_SUPPORTED;
						throw new SAXException();
					}
				} 
			}
			if(attsName.equals(ChessContentHandlerConstants.ATTRIBUTE_TYPE)){
				pointType = atts.getValue(ChessContentHandlerConstants.ATTRIBUTE_1);	
			}
		}else if(atts.getLength() > 1) {
			errorCode = ATTRIBUTE_ERROR;
			throw new SAXException();
		}
	}
	/**
	 * This method will handle the creation of the clock command.
	 * @param value The value that is the to be parsed number of seconds 
	 * @param element The element Time
	 * @throws SAXException Throws and exception if it cannot parse the number
	 */
	private void handleClockValue(String value, String element)throws SAXException{
		try{
			if(board.getGame() instanceof game.ChessGame){
				if(element.equals(ChessContentHandlerConstants.ELEMENT_CURRENT_TIME)){
					time = Long.parseLong(value);
					//System.out.println("Player : " + currentPlayer.getColour() + ", Time : " + time);
					isTimeSet = true;
				}
			}
		} catch (NumberFormatException e){
			errorCode = TIMER_PARSE_ERROR;
			throw new SAXException();
		}
	}
	/**
	 * This method will handle the creation of the start and end location of the piece.
	 * @param value The value to be parsed into a number
	 * @param element The element either X or Y
	 * @throws SAXException Throws and exception if it cannot parse the number
	 */
	private void handleStartValue(String value,String element)throws SAXException{
		try {
			if(element.equals(ChessContentHandlerConstants.ELEMENT_X)){
				int x = Integer.parseInt(value);
				point.setLocation(x, (int)point.getY());
			}else if(element.equals(ChessContentHandlerConstants.ELEMENT_Y)){
				int y = Integer.parseInt(value);
				point.setLocation((int)point.getX(), y);
			}
		} catch (NumberFormatException e) {
			errorCode = POINT_PARSE_ERROR;
			throw new SAXException();
		}
	}
	/**
	 * This method will handle the creation of the counter.
	 * @param value The value to be parsed into a counter number
	 * @param element The element either StartCount or FinalCount
	 * @throws SAXException Throws and exception if it cannot parse the number
	 */
	private void handleCounterValue(String value,String element)throws SAXException{
		try {
			if(element.equals(ChessContentHandlerConstants.ELEMENT_START_COUNT)){
				handleStartValue(value,ChessContentHandlerConstants.ELEMENT_X);
			}else if (element.equals(ChessContentHandlerConstants.ELEMENT_FINAL_COUNT)){
				handleStartValue(value,ChessContentHandlerConstants.ELEMENT_Y);
			}
		} catch (SAXException e) {
			errorCode = COUNT_PARSE_ERROR;
			throw new SAXException();
		}
	}
	/**
	 * This method will return the move list once everything is created
	 * @return Returns a move list
	 */
	public MoveExecutionList getMoveList(){
		return moveList;
	}
	/**
	 * This method will get the current player after or before the parser has started parsing
	 * @return Returns a player object
	 */
	public Player getCurrentPlayer(){
		return currentPlayer;
	}
	/**
	 * This method will return the error code if the parser encounters and error
	 * @return Returns an int representing an error code
	 */
	public int getErrorCode(){
		return errorCode;
	}
	/**
	 * This method will destroy the move list if there is an error in parsing
	 */
	public void destroyList(){
		while(moveList.undo());
	}
}
